﻿using BackupContracts.Sql;
using BackupContracts.Tasks;
using BackupManager.Configuration;
using BackupManager.Sql;
using Microsoft.WindowsAzure.Storage;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;

namespace BackupManager.Tasks
{
    public class TaskManager
    {
        private BackgroundWorker _TimeManager;
        private bool _Enabled;
        private CloudStorageAccount _CloudStorageAccount;

        public TimeSpan Interval { get; set; }

        public TaskManager()
        {
            _TimeManager = new BackgroundWorker();
            _TimeManager.DoWork += _TimeManager_DoWork;
            Interval = TimeSpan.FromMinutes(1);
            MaxTaskRunningTime = 1;
        }

        public void Start()
        {
            _CloudStorageAccount = CloudStorageAccount.Parse(ConfigManager.Instance.StorageConnectionString);
            _Enabled = true;
            if (!_TimeManager.IsBusy)
                _TimeManager.RunWorkerAsync();
        }

        public void Stop()
        {
            _Enabled = false;
        }

        async void _TimeManager_DoWork(object sender, DoWorkEventArgs e)
        {
            while (_Enabled)
            {
                await CheckTasks();
                Thread.Sleep(Interval);
            }
        }

        private async System.Threading.Tasks.Task CheckTasks()
        {
            try
            {
                var currentDateTime = new DateTimeOffset(DateTime.UtcNow);

                var taskProvider = new TaskProvider(_CloudStorageAccount);
                var tasks = taskProvider.ListTasks();

                var maintenanceTasks = taskProvider.ListMaintenanceTasks();
                if (maintenanceTasks.Count == 0)
                {
                    AddMaintenanceTasks(taskProvider);
                    maintenanceTasks = taskProvider.ListMaintenanceTasks();
                }

                var awaitableTasks = new List<System.Threading.Tasks.Task>();

                foreach (var task in tasks)
                {
                    if (task.Enabled && !IsTaskRunning(task.LastExecutionStart, task.LastExecution, currentDateTime))
                    {
                        foreach (var schedule in task.Schedules)
                        {
                            if (NeedsToExecute(schedule, currentDateTime))
                            {
                                awaitableTasks.Add(ExecuteTaskAsync(task, schedule));
                            }
                        }
                    }
                }

                foreach (var maintenanceTask in maintenanceTasks)
                {
                    if (maintenanceTask.Enabled && !IsTaskRunning(maintenanceTask.LastExecutionStart, maintenanceTask.LastExecution, currentDateTime))
                    {
                        if (NeedsToExecute(maintenanceTask, currentDateTime))
                        {
                            awaitableTasks.Add(ExecuteTaskAsync(maintenanceTask));
                        }
                    }
                }

                await System.Threading.Tasks.Task.WhenAll(awaitableTasks.ToArray());
            }
            catch { }
        }

        private void AddMaintenanceTasks(TaskProvider taskProvider)
        {
            var maintenanceTask = new MaintenanceTask()
            {
                Name = "CloseUnfinishedSqlBackups",
                Enabled = true,
                Id = Guid.NewGuid().ToString(),
                RepeatEveryXMinutes = 15,
            };
            taskProvider.UpsertTask(maintenanceTask);

            maintenanceTask = new MaintenanceTask()
            {
                Name = "DeleteUnfinishedSqlBackupCopies",
                Enabled = true,
                Id = Guid.NewGuid().ToString(),
                RepeatEveryXMinutes = 15,
            };
            taskProvider.UpsertTask(maintenanceTask);
        }

        private async System.Threading.Tasks.Task ExecuteTaskAsync(MaintenanceTask maintenanceTask)
        {
            try
            {
                var taskProvider = new TaskProvider(_CloudStorageAccount);

                taskProvider.RefreshTask(maintenanceTask);
                maintenanceTask.LastExecutionStart = new DateTimeOffset(DateTime.UtcNow);
                taskProvider.UpsertTask(maintenanceTask);

                await System.Threading.Tasks.Task.Factory.StartNew(() => ExecuteAction(maintenanceTask));

                taskProvider.RefreshTask(maintenanceTask);
                maintenanceTask.LastExecution = new DateTimeOffset(DateTime.UtcNow);
                taskProvider.UpsertTask(maintenanceTask);
            }
            catch { }
        }

        private void ExecuteAction(MaintenanceTask maintenanceTask)
        {
            if (maintenanceTask.Name.Equals("CloseUnfinishedSqlBackups"))
            {
                var sqlDatabaseProvider = new SqlDatabaseProvider(_CloudStorageAccount, ConfigManager.Instance.EncryptionKey);
                sqlDatabaseProvider.CloseUnfinishedSqlBackups();
            }
            else if (maintenanceTask.Name.Equals("DeleteUnfinishedSqlBackupCopies"))
            {
                var sqlDatabaseProvider = new SqlDatabaseProvider(_CloudStorageAccount, ConfigManager.Instance.EncryptionKey);
                sqlDatabaseProvider.DeleteUnfinishedSqlBackupCopies();
            }
        }

        private bool NeedsToExecute(MaintenanceTask maintenanceTask, DateTimeOffset currentDateTime)
        {
            return (!maintenanceTask.LastExecution.HasValue ||
                    (maintenanceTask.LastExecution.Value.Add(TimeSpan.FromMinutes(maintenanceTask.RepeatEveryXMinutes)) < currentDateTime.UtcDateTime));
        }

        private bool IsTaskRunning(DateTimeOffset? lastExecutionStart, DateTimeOffset? lastExecution, DateTimeOffset currentDateTime)
        {
            if (!lastExecutionStart.HasValue)
                return false;

            var isRunning = lastExecutionStart.Value > lastExecution.GetValueOrDefault().UtcDateTime;

            var isTimedOut = (currentDateTime.UtcDateTime - lastExecutionStart.Value.UtcDateTime).TotalHours > MaxTaskRunningTime;

            return isRunning && !isTimedOut;
        }

        private async System.Threading.Tasks.Task ExecuteTaskAsync(Task task, Schedule schedule)
        {
            try
            {
                var taskProvider = new TaskProvider(_CloudStorageAccount);

                taskProvider.RefreshTask(task);
                task.LastExecutionStart = new DateTimeOffset(DateTime.UtcNow);
                taskProvider.UpsertTask(task);

                var allTasks = new List<System.Threading.Tasks.Task>();
                foreach (var action in task.Actions)
                {
                    allTasks.Add(System.Threading.Tasks.Task.Factory.StartNew(() => ExecuteAction(task, schedule, action)));
                }
                await System.Threading.Tasks.Task.WhenAll(allTasks.ToArray());

                taskProvider.RefreshTask(task);
                task.LastExecution = new DateTimeOffset(DateTime.UtcNow);
                schedule.LastExecution = task.LastExecution;
                taskProvider.UpsertTask(task);
            }
            catch { }
        }

        private void ExecuteAction(Task task, Schedule schedule, TaskAction action)
        {
            if (action is SqlBackupTaskAction)
            {
                var sqlBackupTaskAction = (SqlBackupTaskAction)action;

                var sqlDatabaseProvider = new SqlDatabaseProvider(_CloudStorageAccount, ConfigManager.Instance.EncryptionKey);
                sqlBackupTaskAction.Database.Server = sqlDatabaseProvider.GetServer(sqlBackupTaskAction.Database.ServerId);

                SqlDatabaseProvider.CopyAndBackupToBlob(
                    sqlBackupTaskAction.Database.Server.Name,
                    sqlBackupTaskAction.Database.Name,
                    sqlBackupTaskAction.Database.Server.User,
                    sqlBackupTaskAction.Database.Server.Password,
                    _CloudStorageAccount,
                    ConfigManager.Instance.DefaultSqlBackupContainerName,
                    ConfigManager.Instance.MaximumNumberOfBackupCopies);
            }
        }

        private bool NeedsToExecute(Schedule schedule, DateTimeOffset currentDateTime)
        {
            switch (schedule.ScheduleType)
            {
                default:
                case ScheduleType.OneTime:
                    return (schedule.Start.UtcDateTime <= currentDateTime.UtcDateTime &&
                            schedule.LastExecution == null);
                case ScheduleType.Daily:
                    return (schedule.Start.UtcDateTime <= currentDateTime.UtcDateTime &&
                            (schedule.Start.UtcDateTime.TimeOfDay <= currentDateTime.UtcDateTime.TimeOfDay || schedule.LastExecution == null) &&
                            (
                                schedule.LastExecution == null
                                || (currentDateTime.UtcDateTime - schedule.LastExecution.Value.UtcDateTime).TotalDays >= schedule.RecurEveryX
                            ));
                case ScheduleType.Weekly:
                    return (schedule.Start.UtcDateTime <= currentDateTime.UtcDateTime &&
                            (schedule.Start.UtcDateTime.TimeOfDay <= currentDateTime.UtcDateTime.TimeOfDay || schedule.LastExecution == null) &&
                            schedule.DaysOfWeek.Any((d) => d.Selected && d.Id == (int)currentDateTime.UtcDateTime.DayOfWeek) &&
                            (
                                schedule.LastExecution == null
                                || (currentDateTime.UtcDateTime - schedule.LastExecution.Value.UtcDateTime).TotalDays / 7 >= schedule.RecurEveryX
                            ));
                case ScheduleType.Monthly:
                    return (schedule.Start.UtcDateTime <= currentDateTime.UtcDateTime &&
                            (schedule.Start.UtcDateTime.TimeOfDay <= currentDateTime.UtcDateTime.TimeOfDay || schedule.LastExecution == null) &&
                            schedule.Days.Any((d) => d.Selected && d.Id == currentDateTime.UtcDateTime.Day) &&
                            schedule.Months.Any((d) => d.Selected && d.Id == currentDateTime.UtcDateTime.Month) &&
                            (
                                schedule.LastExecution == null
                                || (currentDateTime.UtcDateTime - schedule.LastExecution.Value.UtcDateTime).TotalDays >= 1
                            ));
            }
        }

        public double MaxTaskRunningTime { get; set; }
    }
}
